/*
 * %W% %E%
 *
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package com.sun.corba.se.impl.orb ;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress ;
import java.security.PrivilegedAction ;
import java.security.PrivilegedExceptionAction ;
import java.security.AccessController ;
import java.util.Collection ;
import java.util.Iterator ;

import org.omg.CORBA.CompletionStatus ;
import org.omg.CORBA.portable.ValueFactory ;

import com.sun.corba.se.pept.protocol.ClientRequestDispatcher ;
import com.sun.corba.se.pept.transport.Acceptor;

import com.sun.corba.se.spi.activation.Locator ;
import com.sun.corba.se.spi.activation.Activator ;
import com.sun.corba.se.spi.activation.LocatorHelper ;
import com.sun.corba.se.spi.activation.ActivatorHelper ;
import com.sun.corba.se.spi.activation.EndPointInfo ;

import com.sun.corba.se.spi.copyobject.ObjectCopierFactory ;
import com.sun.corba.se.spi.copyobject.CopyobjectDefaults ;
import com.sun.corba.se.spi.copyobject.CopierManager ;

import com.sun.corba.se.spi.ior.IdentifiableFactoryFinder ;
import com.sun.corba.se.spi.ior.IOR;
import com.sun.corba.se.spi.ior.IORFactories ;

import com.sun.corba.se.spi.ior.iiop.IIOPFactories ;

import com.sun.corba.se.spi.legacy.connection.ORBSocketFactory;

import com.sun.corba.se.spi.logging.CORBALogDomains ;

import com.sun.corba.se.spi.oa.OADefault ;
import com.sun.corba.se.spi.oa.ObjectAdapter ;
import com.sun.corba.se.spi.oa.ObjectAdapterFactory ;

import com.sun.corba.se.spi.orb.Operation ;
import com.sun.corba.se.spi.orb.OperationFactory ;
import com.sun.corba.se.spi.orb.ORBData ;
import com.sun.corba.se.spi.orb.DataCollector ;
import com.sun.corba.se.spi.orb.ORBConfigurator ;
import com.sun.corba.se.spi.orb.ParserImplBase ;
import com.sun.corba.se.spi.orb.PropertyParser ;
import com.sun.corba.se.spi.orb.ORB ;

import com.sun.corba.se.spi.orbutil.closure.Closure ;
import com.sun.corba.se.spi.orbutil.closure.ClosureFactory ;

import com.sun.corba.se.spi.protocol.RequestDispatcherRegistry ;
import com.sun.corba.se.spi.protocol.CorbaServerRequestDispatcher ;
import com.sun.corba.se.spi.protocol.RequestDispatcherDefault ;
import com.sun.corba.se.spi.protocol.LocalClientRequestDispatcherFactory ;

import com.sun.corba.se.spi.resolver.LocalResolver ;
import com.sun.corba.se.spi.resolver.Resolver ;
import com.sun.corba.se.spi.resolver.ResolverDefault ;

import com.sun.corba.se.spi.transport.CorbaContactInfoList;
import com.sun.corba.se.spi.transport.CorbaContactInfoListFactory;
import com.sun.corba.se.spi.transport.SocketInfo;
import com.sun.corba.se.spi.transport.TransportDefault ;

import com.sun.corba.se.spi.presentation.rmi.PresentationManager ;
import com.sun.corba.se.spi.presentation.rmi.PresentationDefaults ;

import com.sun.corba.se.spi.servicecontext.ServiceContext ;
import com.sun.corba.se.spi.servicecontext.ServiceContextRegistry ;
import com.sun.corba.se.spi.servicecontext.UEInfoServiceContext ;
import com.sun.corba.se.spi.servicecontext.CodeSetServiceContext ;
import com.sun.corba.se.spi.servicecontext.SendingContextServiceContext ;
import com.sun.corba.se.spi.servicecontext.ORBVersionServiceContext ;
import com.sun.corba.se.spi.servicecontext.MaxStreamFormatVersionServiceContext ;
import com.sun.corba.se.impl.logging.ORBUtilSystemException ;
import com.sun.corba.se.impl.transport.SocketOrChannelAcceptorImpl;

// XXX This should go away once we get rid of the port exchange for ORBD
import com.sun.corba.se.spi.legacy.connection.LegacyServerSocketEndPointInfo;
import com.sun.corba.se.impl.legacy.connection.SocketFactoryAcceptorImpl;
import com.sun.corba.se.impl.legacy.connection.SocketFactoryContactInfoListImpl;
import com.sun.corba.se.impl.legacy.connection.USLPort;

// XXX These should move to SPI
import com.sun.corba.se.impl.orbutil.ORBConstants ;

// XXX This needs an SPI
import com.sun.corba.se.impl.dynamicany.DynAnyFactoryImpl ;

public class ORBConfiguratorImpl implements ORBConfigurator {
    private ORBUtilSystemException wrapper ;

    public static class ConfigParser extends ParserImplBase {
	public Class[] userConfigurators = null ;

	public PropertyParser makeParser()
	{
	    PropertyParser parser = new PropertyParser() ;
	    Operation action = OperationFactory.compose( 
		OperationFactory.suffixAction(),
		OperationFactory.classAction() 
	    ) ;
	    parser.addPrefix( ORBConstants.SUN_PREFIX + "ORBUserConfigurators",
		action, "userConfigurators", Class.class ) ;
	    return parser ;
	}
    }

    public void configure( DataCollector collector, ORB orb ) 
    {
	ORB theOrb = orb ;
	wrapper = ORBUtilSystemException.get( orb, CORBALogDomains.ORB_LIFECYCLE ) ;

	initObjectCopiers( theOrb ) ;
	initIORFinders( theOrb ) ;

	theOrb.setClientDelegateFactory( 
            // REVISIT: this should be ProtocolDefault.
	    TransportDefault.makeClientDelegateFactory( theOrb )) ;

	initializeTransport(theOrb) ;

	initializeNaming( theOrb ) ;
	initServiceContextRegistry( theOrb ) ;
	initRequestDispatcherRegistry( theOrb ) ;
	registerInitialReferences( theOrb ) ;

	persistentServerInitialization( theOrb ) ;

	runUserConfigurators( collector, theOrb ) ;
    }

    private void runUserConfigurators( DataCollector collector, ORB orb ) 
    {
	// Run any pluggable configurators.  This is a lot like 
	// ORBInitializers, only it uses the internal ORB and has
	// access to all data for parsing.  
	ConfigParser parser = new ConfigParser()  ;
	parser.init( collector ) ;
	if (parser.userConfigurators != null) {
	    for (int ctr=0; ctr<parser.userConfigurators.length; ctr++) {
		Class cls = parser.userConfigurators[ctr] ;
		try {
		    ORBConfigurator config = (ORBConfigurator)(cls.newInstance()) ;
		    config.configure( collector, orb ) ;
		} catch (Exception exc) {
		    // XXX Log this exception
		    // ignore this for now: a bad user configurator does nothing
		}
	    }
	}
    }

    private void persistentServerInitialization( ORB orb )
    {
	ORBData data = orb.getORBData() ;

	// determine the ORBD port so that persistent objrefs can be
	// created.
	if (data.getServerIsORBActivated()) {
	    try {
                Locator locator = LocatorHelper.narrow(
		    orb.resolve_initial_references( 
			ORBConstants.SERVER_LOCATOR_NAME )) ;
                Activator activator = ActivatorHelper.narrow(
		    orb.resolve_initial_references( 
			ORBConstants.SERVER_ACTIVATOR_NAME )) ;
		Collection serverEndpoints = 
		    orb.getCorbaTransportManager().getAcceptors(null, null);
		EndPointInfo[] endpointList = 
		    new EndPointInfo[serverEndpoints.size()];
		Iterator iterator = serverEndpoints.iterator();
		int i = 0 ;
		while (iterator.hasNext()) {
		    Object n = iterator.next();
		    if (! (n instanceof LegacyServerSocketEndPointInfo)) {
			continue;
		    }
		    LegacyServerSocketEndPointInfo ep = 
			(LegacyServerSocketEndPointInfo) n;
		    // REVISIT - use exception instead of -1.
		    int port = locator.getEndpoint(ep.getType());
		    if (port == -1) {
			port = locator.getEndpoint(SocketInfo.IIOP_CLEAR_TEXT);
			if (port == -1) {
			    throw new Exception(
				"ORBD must support IIOP_CLEAR_TEXT");
			}
		    }

		    ep.setLocatorPort(port);
		    
		    endpointList[i++] = 
			new EndPointInfo(ep.getType(), ep.getPort());
		}

	        activator.registerEndpoints(
		    data.getPersistentServerId(), data.getORBId(), 
			endpointList);
	    } catch (Exception ex) {
		throw wrapper.persistentServerInitError( 
		    CompletionStatus.COMPLETED_MAYBE, ex ) ;
	    }
	}
    }

    /**
     * This is made somewhat complex because we are currently supporting
     * the ContactInfoList/Acceptor *AND* the legacy SocketFactory 
     * transport architecture.
     */
    private void initializeTransport(final ORB orb)
    {
	ORBData od = orb.getORBData();

	CorbaContactInfoListFactory contactInfoListFactory =
	    od.getCorbaContactInfoListFactory();
	Acceptor[] acceptors = od.getAcceptors();

	// BEGIN Legacy
	ORBSocketFactory legacySocketFactory = od.getLegacySocketFactory();
	USLPort[] uslPorts = od.getUserSpecifiedListenPorts() ;
	setLegacySocketFactoryORB(orb, legacySocketFactory);
	// END Legacy

	//
	// Check for incorrect configuration.
	//

	if (legacySocketFactory != null && contactInfoListFactory != null) {
	    throw wrapper.socketFactoryAndContactInfoListAtSameTime();
	}

	if (acceptors.length != 0 && legacySocketFactory != null) {
	    throw wrapper.acceptorsAndLegacySocketFactoryAtSameTime();
	}


	//
	// Client and Server side setup.
	//

	od.getSocketFactory().setORB(orb);

	//
	// Set up client side.
	//

	if (legacySocketFactory != null) {
	    // BEGIN Legacy
	    // Since the user specified a legacy socket factory we need to
	    // use a ContactInfoList that will use the legacy socket factory.
	    contactInfoListFactory =
		new CorbaContactInfoListFactory() {
			public void setORB(ORB orb) { }
			public CorbaContactInfoList create( IOR ior ) {
			    return new SocketFactoryContactInfoListImpl( 
                                orb, ior);
			}
		    };
	    // END Legacy
	} else if (contactInfoListFactory != null) {
	    // The user specified an explicit ContactInfoListFactory.
	    contactInfoListFactory.setORB(orb);
	} else {
	    // Use the default.
	    contactInfoListFactory =
	        TransportDefault.makeCorbaContactInfoListFactory(orb);
	}
	orb.setCorbaContactInfoListFactory(contactInfoListFactory);

	//
	// Set up server side.
	//

	//
	// Maybe allocate the Legacy default listener.
	//
	// If old legacy properties set, or there are no explicit
	// acceptors then register a default listener.  Type of
	// default listener depends on presence of legacy socket factory.
	//
	// Note: this must happen *BEFORE* registering explicit acceptors.
	//

	// BEGIN Legacy
	int port = -1;
	if (od.getORBServerPort() != 0) {
	    port = od.getORBServerPort();
	} else if (od.getPersistentPortInitialized()) {
	    port = od.getPersistentServerPort();
	} else if (acceptors.length == 0) {
	    port = 0;
	}
	if (port != -1) {
	    createAndRegisterAcceptor(orb, legacySocketFactory, port,
			LegacyServerSocketEndPointInfo.DEFAULT_ENDPOINT,
			SocketInfo.IIOP_CLEAR_TEXT);
	}
	// END Legacy

	for (int i = 0; i < acceptors.length; i++) {
	    orb.getCorbaTransportManager().registerAcceptor(acceptors[i]);
	}

	// BEGIN Legacy
	// Allocate user listeners.
	USLPort[] ports = od.getUserSpecifiedListenPorts() ;
	if (ports != null) {
	    for (int i = 0; i < ports.length; i++) {
		createAndRegisterAcceptor(
                    orb, legacySocketFactory, ports[i].getPort(),
		    LegacyServerSocketEndPointInfo.NO_NAME,
		    ports[i].getType());
	    }
	}
	// END Legacy
    }

    /*
     * Legacy: name.
     */
    // REVISIT: see ORBD. make factory in TransportDefault.
    private void createAndRegisterAcceptor(ORB orb,
					   ORBSocketFactory legacySocketFactory,
					   int port, String name, String type)
    {
	Acceptor acceptor;
	if (legacySocketFactory == null) {
	    acceptor =
		new SocketOrChannelAcceptorImpl(orb, port, name, type);
	} else {
	    acceptor =
		new SocketFactoryAcceptorImpl(orb, port, name, type);
	}
	orb.getTransportManager().registerAcceptor(acceptor);
    }

    private void setLegacySocketFactoryORB(
        final ORB orb, final ORBSocketFactory legacySocketFactory)
    {
	if (legacySocketFactory == null) {
	    return;
	}

	// Note: the createServerSocket and createSocket methods on the
	// DefaultSocketFactory need to get data from the ORB but
	// we cannot change the interface.  So set the ORB (if it's ours)
	// by reflection.

	try {
    	    AccessController.doPrivileged(
                new PrivilegedExceptionAction() {
	            public Object run()
		        throws InstantiationException, IllegalAccessException
		    {
			try {
			    Class[] params = { ORB.class };
			    Method method =
				legacySocketFactory.getClass().getMethod(
                                  "setORB", params);
			    Object[] args = { orb };
			    method.invoke(legacySocketFactory, args);
			} catch (NoSuchMethodException e) {
			    // NOTE: If there is no method then it
			    // is not ours - so ignore it.
			    ;
			} catch (IllegalAccessException e) {
			    RuntimeException rte = new RuntimeException();
			    rte.initCause(e);
			    throw rte;
			} catch (InvocationTargetException e) {
			    RuntimeException rte = new RuntimeException();
			    rte.initCause(e);
			    throw rte;
			}
			return null;
		    }
		}
	    );
	} catch (Throwable t) {
	    throw wrapper.unableToSetSocketFactoryOrb(t);
	}
    }

    private void initializeNaming( ORB orb )
    { 
	LocalResolver localResolver = ResolverDefault.makeLocalResolver() ;
	orb.setLocalResolver( localResolver ) ;

	Resolver bootResolver = ResolverDefault.makeBootstrapResolver( orb,
	    orb.getORBData().getORBInitialHost(),
	    orb.getORBData().getORBInitialPort() ) ;

	Operation urlOperation = ResolverDefault.makeINSURLOperation( orb,
	    bootResolver ) ;
	orb.setURLOperation( urlOperation ) ;

	Resolver irResolver = ResolverDefault.makeORBInitRefResolver( urlOperation,
	    orb.getORBData().getORBInitialReferences() ) ;

	Resolver dirResolver = ResolverDefault.makeORBDefaultInitRefResolver( 
	    urlOperation, orb.getORBData().getORBDefaultInitialReference() ) ;

	Resolver resolver = 
	    ResolverDefault.makeCompositeResolver( localResolver,
		ResolverDefault.makeCompositeResolver( irResolver,
		    ResolverDefault.makeCompositeResolver( dirResolver, 
			bootResolver ) ) ) ;
	orb.setResolver( resolver ) ;
    }

    private void initServiceContextRegistry( ORB orb ) 
    {
	ServiceContextRegistry scr = orb.getServiceContextRegistry() ;

	scr.register( UEInfoServiceContext.class ) ;
	scr.register( CodeSetServiceContext.class ) ;
	scr.register( SendingContextServiceContext.class ) ;
	scr.register( ORBVersionServiceContext.class ) ;
	scr.register( MaxStreamFormatVersionServiceContext.class ) ;
    }

    private void registerInitialReferences( final ORB orb ) 
    {
	// Register the Dynamic Any factory
        Closure closure = new Closure() {
            public java.lang.Object evaluate() {
                return new DynAnyFactoryImpl( orb ) ;
            }
        } ;

        Closure future = ClosureFactory.makeFuture( closure ) ;
        orb.getLocalResolver().register( ORBConstants.DYN_ANY_FACTORY_NAME, 
	    future ) ;
    }

    private static final int ORB_STREAM = 0 ;

    private void initObjectCopiers( ORB orb )
    {
	// No optimization or policy selection here.
	ObjectCopierFactory orbStream = 
	    CopyobjectDefaults.makeORBStreamObjectCopierFactory( orb ) ;

	CopierManager cm = orb.getCopierManager() ;
	cm.setDefaultId( ORB_STREAM ) ;

	cm.registerObjectCopierFactory( orbStream, ORB_STREAM ) ;
    }

    private void initIORFinders( ORB orb ) 
    {
	IdentifiableFactoryFinder profFinder = 
	    orb.getTaggedProfileFactoryFinder() ;
	profFinder.registerFactory( IIOPFactories.makeIIOPProfileFactory() ) ;

	IdentifiableFactoryFinder profTempFinder = 
	    orb.getTaggedProfileTemplateFactoryFinder() ;
	profTempFinder.registerFactory( 
	    IIOPFactories.makeIIOPProfileTemplateFactory() ) ;

	IdentifiableFactoryFinder compFinder = 
	    orb.getTaggedComponentFactoryFinder() ;
	compFinder.registerFactory( 
	    IIOPFactories.makeCodeSetsComponentFactory() ) ;
	compFinder.registerFactory( 
	    IIOPFactories.makeJavaCodebaseComponentFactory() ) ;
	compFinder.registerFactory( 
	    IIOPFactories.makeORBTypeComponentFactory() ) ;
	compFinder.registerFactory( 
	    IIOPFactories.makeMaxStreamFormatVersionComponentFactory() ) ;
	compFinder.registerFactory( 
	    IIOPFactories.makeAlternateIIOPAddressComponentFactory() ) ;
	compFinder.registerFactory( 
	    IIOPFactories.makeRequestPartitioningComponentFactory() ) ;
	compFinder.registerFactory(
	    IIOPFactories.makeJavaSerializationComponentFactory());

	// Register the ValueFactory instances for ORT
	IORFactories.registerValueFactories( orb ) ;

	// Register an ObjectKeyFactory
	orb.setObjectKeyFactory( IORFactories.makeObjectKeyFactory(orb) ) ;
    }

    private void initRequestDispatcherRegistry( ORB orb ) 
    {
	RequestDispatcherRegistry scr = orb.getRequestDispatcherRegistry() ;

	// register client subcontracts
	ClientRequestDispatcher csub = 
	    RequestDispatcherDefault.makeClientRequestDispatcher() ;
	scr.registerClientRequestDispatcher( csub, 
	    ORBConstants.TOA_SCID ) ;
	scr.registerClientRequestDispatcher( csub, 
	    ORBConstants.TRANSIENT_SCID ) ;
	scr.registerClientRequestDispatcher( csub, 
	    ORBConstants.PERSISTENT_SCID ) ;
	scr.registerClientRequestDispatcher( csub, 
	    ORBConstants.SC_TRANSIENT_SCID ) ;
	scr.registerClientRequestDispatcher( csub, 
	    ORBConstants.SC_PERSISTENT_SCID ) ;
	scr.registerClientRequestDispatcher( csub,  
	    ORBConstants.IISC_TRANSIENT_SCID ) ;
	scr.registerClientRequestDispatcher( csub, 
	    ORBConstants.IISC_PERSISTENT_SCID ) ;
	scr.registerClientRequestDispatcher( csub, 
	    ORBConstants.MINSC_TRANSIENT_SCID ) ;
	scr.registerClientRequestDispatcher( csub, 
	    ORBConstants.MINSC_PERSISTENT_SCID ) ;
	
	// register server delegates
	CorbaServerRequestDispatcher sd = 
	    RequestDispatcherDefault.makeServerRequestDispatcher( orb );
	scr.registerServerRequestDispatcher( sd, 
	    ORBConstants.TOA_SCID ) ;
	scr.registerServerRequestDispatcher( sd, 
	    ORBConstants.TRANSIENT_SCID ) ;
	scr.registerServerRequestDispatcher( sd, 
	    ORBConstants.PERSISTENT_SCID ) ;
	scr.registerServerRequestDispatcher( sd, 
	    ORBConstants.SC_TRANSIENT_SCID ) ;
	scr.registerServerRequestDispatcher( sd, 
	    ORBConstants.SC_PERSISTENT_SCID ) ;
	scr.registerServerRequestDispatcher( sd, 
	    ORBConstants.IISC_TRANSIENT_SCID ) ;
	scr.registerServerRequestDispatcher( sd, 
	    ORBConstants.IISC_PERSISTENT_SCID ) ;
	scr.registerServerRequestDispatcher( sd, 
	    ORBConstants.MINSC_TRANSIENT_SCID ) ;
	scr.registerServerRequestDispatcher( sd, 
	    ORBConstants.MINSC_PERSISTENT_SCID ) ;
	
	orb.setINSDelegate( 
	    RequestDispatcherDefault.makeINSServerRequestDispatcher( orb ) ) ;
	    
	// register local client subcontracts
	LocalClientRequestDispatcherFactory lcsf = 
	    RequestDispatcherDefault.makeJIDLLocalClientRequestDispatcherFactory( 
		orb ) ;
	scr.registerLocalClientRequestDispatcherFactory( lcsf, 
	    ORBConstants.TOA_SCID ) ;

	lcsf = 
	    RequestDispatcherDefault.makePOALocalClientRequestDispatcherFactory( 
		orb ) ;
	scr.registerLocalClientRequestDispatcherFactory( lcsf, 
	    ORBConstants.TRANSIENT_SCID ) ;
	scr.registerLocalClientRequestDispatcherFactory( lcsf, 
	    ORBConstants.PERSISTENT_SCID ) ;

	lcsf = RequestDispatcherDefault.
	    makeFullServantCacheLocalClientRequestDispatcherFactory( orb ) ;
	scr.registerLocalClientRequestDispatcherFactory( lcsf, 
	    ORBConstants.SC_TRANSIENT_SCID ) ;
	scr.registerLocalClientRequestDispatcherFactory( lcsf, 
	    ORBConstants.SC_PERSISTENT_SCID ) ;

	lcsf = RequestDispatcherDefault.
	    makeInfoOnlyServantCacheLocalClientRequestDispatcherFactory( orb ) ;
	scr.registerLocalClientRequestDispatcherFactory( lcsf, 
	    ORBConstants.IISC_TRANSIENT_SCID ) ;
	scr.registerLocalClientRequestDispatcherFactory( lcsf, 
	    ORBConstants.IISC_PERSISTENT_SCID ) ;

	lcsf = RequestDispatcherDefault.
	    makeMinimalServantCacheLocalClientRequestDispatcherFactory( orb ) ;
	scr.registerLocalClientRequestDispatcherFactory( lcsf, 
	    ORBConstants.MINSC_TRANSIENT_SCID ) ;
	scr.registerLocalClientRequestDispatcherFactory( lcsf, 
	    ORBConstants.MINSC_PERSISTENT_SCID ) ;

	/* Register the server delegate that implements the ancient bootstrap
	 * naming protocol.  This takes an object key of either "INIT" or 
	 * "TINI" to allow for big or little endian implementations.
	 */
	CorbaServerRequestDispatcher bootsd = 
	    RequestDispatcherDefault.makeBootstrapServerRequestDispatcher( 
		orb ) ;
	scr.registerServerRequestDispatcher( bootsd, "INIT" ) ;
	scr.registerServerRequestDispatcher( bootsd, "TINI" ) ;

	// Register object adapter factories
	ObjectAdapterFactory oaf = OADefault.makeTOAFactory( orb ) ;
	scr.registerObjectAdapterFactory( oaf, ORBConstants.TOA_SCID ) ;

	oaf = OADefault.makePOAFactory( orb ) ;
	scr.registerObjectAdapterFactory( oaf, ORBConstants.TRANSIENT_SCID ) ;
	scr.registerObjectAdapterFactory( oaf, ORBConstants.PERSISTENT_SCID ) ;
	scr.registerObjectAdapterFactory( oaf, ORBConstants.SC_TRANSIENT_SCID ) ;
	scr.registerObjectAdapterFactory( oaf, ORBConstants.SC_PERSISTENT_SCID ) ;
	scr.registerObjectAdapterFactory( oaf, ORBConstants.IISC_TRANSIENT_SCID ) ;
	scr.registerObjectAdapterFactory( oaf, ORBConstants.IISC_PERSISTENT_SCID ) ;
	scr.registerObjectAdapterFactory( oaf, ORBConstants.MINSC_TRANSIENT_SCID ) ;
	scr.registerObjectAdapterFactory( oaf, ORBConstants.MINSC_PERSISTENT_SCID ) ;
    } 
}

// End of file.
